package cn.edu.usst.cs.pi;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class Main {
    /**
     * the path to a txt file storing 100b digits of pi<br/>
     * we decided to use the pi value others have calculated using super computers
     */
    static String PI_DATA = "E:\\pi_dec_1t_01\\pi_dec_1t_01.txt";
    /**
     * the target
     */
    static final String N = "6939937510";
    final static int blockSize = N.length();
    /**
     * output of reader/ inputstream should be directly stored here
     */
    static byte[] bufferFromFile = null;

    /**
     * only accepts buffer from {@code bufferFromFile}
     */
    static GoldfishStack bufferForCompareThread = null;
    /**
     * The size of {@code bufferForCompareThread}should always be {@code N.length*(REPEAT+1)}<br/>
     * In this way, {@code bufferFromFile} reads the {@code 0~(N.length-1) }digits <br/>
     * and append them to  {@code bufferForCompareThread}<br/>
     * Then {@code bufferFromFile} reads the {@code N.length~(2*N.length-1) } digits<br/> and append them to  {@code bufferForCompareThread}
     * ...<br/>
     * Until  {@code bufferFromFile} reads the {@code REPEAT*N.length~(REPEAT+1)*N.length-1) } digits<br/> and append them to  {@code bufferForCompareThread}
     * at this time, a new candidate for {@code bufferForCompareThread} will be newed<br/>
     * bytes {@code bufferFromFile} got will append both the existing and the new {@code bufferForCompareThread}<br/>
     * after this, a new {@code Comparer} will be newed with the existing {@code bufferFromFile} and be submitted for comparing
     */
    final static int REPEAT = 1000;

    /**
     * change this according to the computability of your own machine
     */
    final static int THREADS = 16;

    /**
     * @param args
     * @throws IOException
     */
    static ExecutorService executor;
    static List<Future<List<Integer>>> futures;
    static int offset;
    static int blockCount;

    static {
        bufferFromFile = new byte[N.length()];
        executor = Executors.newFixedThreadPool(THREADS);
        futures = new ArrayList<>();
        bufferForCompareThread = new GoldfishStack(N.length() * (REPEAT + 1));
        offset = 0;
        blockCount = 0;
    }

    public static void main(String[] args) throws IOException {
        File file = new File(PI_DATA);
        System.out.println(file.length());

        try (FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
            GoldfishStack newBufferForCompareThread = null;
            while (true) {
                int bytesRead = bufferedInputStream.read(bufferFromFile);
                EOF:
                {
                    if (bytesRead == blockSize) break EOF;
                    if (bytesRead >= 0) {
                        if (!bufferForCompareThread.addAll(bufferFromFile, 0, bytesRead)) throw new EOFException();
                        offset += bytesRead;
                    }
                    futures.add(executor.submit(new Comparer(offset, bufferForCompareThread.getStack())));
                    break;
                }
                //normal
                offset += bytesRead;
                if (blockCount < REPEAT) {
                    bufferForCompareThread.addAll(bufferFromFile, 0, bytesRead);
                    blockCount++;
                    continue;
                }
                switch (blockCount % REPEAT) {
                    case 0:
                        newBufferForCompareThread = new GoldfishStack(N.length() * (REPEAT + 1));
                        newBufferForCompareThread.addAll(bufferFromFile, 0, bytesRead);
                        bufferForCompareThread.addAll(bufferFromFile, 0, bytesRead);
                        break;
                    case 1:
                        futures.add(executor.submit(new Comparer(offset, bufferForCompareThread.getStack())));
                        newBufferForCompareThread.addAll(bufferFromFile, 0, bytesRead);
                        bufferForCompareThread = newBufferForCompareThread;
                        System.out.println("submitted"+blockCount);
                        break;
                    default:
                        bufferForCompareThread.addAll(bufferFromFile, 0, bytesRead);
                        break;
                }
                blockCount++;
            }
        }

        List<Integer> results = new ArrayList<>();

        for (var result : futures) {
            try {
                results.addAll(result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

}

class Comparer implements Callable<List<Integer>> {
    int offset;
    byte[] buffer;

    public Comparer(int offset, byte[] buffer) {
        this.offset = offset;
        this.buffer = buffer;
    }

    @Override
    public List<Integer> call() throws Exception {
        List<Integer> result = new ArrayList<>();
        byte[] key = Main.N.getBytes();
        for (int i = 0; i < buffer.length - Main.N.length(); i++) {
            int j = 0;
            for (; j < Main.N.length(); j++) {
                if (!(key[i] == buffer[i + j])) break;
            }
            if (j == Main.N.length())
                result.add(i);
        }
        System.out.printf("offset %d: %s\n", offset, result);
        return result;
    }

    @Override
    public String toString() {
        return new String(buffer, 0, offset);
    }
}

class GoldfishStack {
    private final byte[] stack;
    private int size;
    final int MAX;

    public GoldfishStack(int MAX) {
        this.MAX = MAX;
        stack = new byte[MAX];
        size = 0;
    }

    public boolean addAll(byte[] src, int start, int end) {
        if (end - start > MAX - size) return false;
        for (int i = start; i < end; i++) {
            stack[size++] = src[i];
        }
        return true;
    }

    public byte[] getStack() {
        return stack;
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        String buffer = new String(stack, 0, size);
        return size + ":" + buffer;
    }
}